package uniandes.simulacion.transportame;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;

/**
 * Esta clase modela el sistema con la segunda configuración
 */
public class Principal2 {

	public static final int FILA_1 = 1;
	public static final int FILA_2 = 2;
	public static final int NO_ASIGNADO = 0;
	public static final int ARRIBO = 0;
	public static final int REQUISA = 1;
	public static final int SALIDA = 2;
	public static final double tiempoCaminata = 0.000000000001;

	private double tiempoSimulacion;
	private double tiempoActual;

	private int numeroEventos;
	private int numeroTotalPasajeros;

	private ArrayList<Pasajero> noAtendidos, atendidos, fila1, fila2;
	private ArrayList<Evento> eventos;

	/**
	 * Crea una nueva clase de la aplicación
	 */
	public Principal2() {
		inicializar();
		comenzar();
//		imprimirCorrida();
		darEstadisticos();
	}

	/**
	 * Inicializa la simulación
	 */
	private void inicializar() {
		numeroEventos = 0;
		numeroTotalPasajeros = 0;
		tiempoSimulacion = 60*24;
		double lambda = 10;
		tiempoActual = 0;
		eventos = new ArrayList<Evento>();
		noAtendidos = new ArrayList<Pasajero>();
		atendidos = new ArrayList<Pasajero>();
		fila1 = new ArrayList<Pasajero>();
		fila2 = new ArrayList<Pasajero>();

		double tiempo = 0;

		Pasajero nuevo = new Pasajero(0, Double.MAX_VALUE, Double.MAX_VALUE,
				Double.MAX_VALUE, NO_ASIGNADO, 0);
		noAtendidos.add(nuevo);
		int id = 1;
		while (tiempo < tiempoSimulacion) {
			double tiempoLlegada = tiempo + genInstExpo(lambda);
			nuevo = new Pasajero(tiempoLlegada, Double.MAX_VALUE,
					Double.MAX_VALUE, Double.MAX_VALUE, NO_ASIGNADO, id);
			noAtendidos.add(nuevo);

			tiempo = tiempoLlegada;
			id++;
		}

	}

	/**
	 * Da los estadísticos de la corrida
	 */
	private void darEstadisticos() {
		int numeroPasajeros = atendidos.size();
		double tiempoTotalCola = 0;
		double tiempoUsadoGuardia1 = 0, tiempoUsadoGuardia2 = 0;

		for (Pasajero p : atendidos) {
			tiempoTotalCola += (p.getFila() == FILA_1) ? p.getTiempoInicioRequisaFila1() - p.getTiempoLlegada() : 
				p.getTiempoInicioRequisaFila1() == Double.MAX_VALUE ? p.getTiempoInicioRequisaFila2() - p.getTiempoLlegada() : p
						.getTiempoInicioRequisaFila1()
						- p.getTiempoLlegada()
						+ p.getTiempoInicioRequisaFila2()
						- (p.getTiempoInicioRequisaFila1() + 0.25);

			tiempoUsadoGuardia1 += (p.getFila() == FILA_1) ? p
					.getTiempoSalida() - p.getTiempoInicioRequisaFila1() : p.getTiempoInicioRequisaFila1() == Double.MAX_VALUE ? 0: 0.25;
			
			tiempoUsadoGuardia2 += (p.getFila() == FILA_1) ? 0 : p.getTiempoSalida() - p.getTiempoInicioRequisaFila2();
		}

		double tiempoPromedioCola = tiempoTotalCola / numeroPasajeros;
		double utilizacionGuardia1 = tiempoUsadoGuardia1 / tiempoSimulacion;
		double utilizacionGuardia2 = tiempoUsadoGuardia2 / tiempoSimulacion;
		double numeroPromedioPasajeros = (double) numeroTotalPasajeros
				/ numeroEventos;

		DecimalFormat df = new DecimalFormat("#.#####");

		System.out.println(df.format(tiempoPromedioCola) + '\t'
				+ df.format(utilizacionGuardia1) + '\t'
				+ df.format(utilizacionGuardia2) + '\t'
				+ df.format(numeroPromedioPasajeros));

	}

	/**
	 * Inicia la simulación
	 */
	private void comenzar() {
		// Inicia creando los eventos de llegada para todos los pasajeros

		while (!noAtendidos.isEmpty()) {
			Pasajero actual = noAtendidos.get(0);
			Evento llegadaActual = new Evento(actual.getTiempoLlegada(),
					ARRIBO, actual);
			eventos.add(llegadaActual);

			noAtendidos.remove(actual);
			atendidos.add(actual);
		}

		double tiempoTerminacionUltimaRequisaFila1 = 0;
		double tiempoTerminacionUltimaRequisaFila2 = 0;
		double tiempoTerminacionUltimaIncompletaFila2 = 0;

		while (!eventos.isEmpty()) {

			Evento proximo = darProximoEvento();
			eventos.remove(proximo);
			
			numeroTotalPasajeros += fila1.size() + fila2.size();
			numeroEventos++;

			tiempoActual = proximo.getInicio();

			int tipo = proximo.getTipo();
			Pasajero asociado = proximo.getAsociado();

			if (tipo == ARRIBO) {
				if (fila1.size() + 1 == 14) {
										
					for(int i=7;i<fila1.size();i++)
					{
						Pasajero actual = fila1.get(i);
						actual.setFila(FILA_2);
						double tiempoInicioRequisa = Math.max(
								tiempoTerminacionUltimaRequisaFila2, tiempoActual);
						
						actual.setTiempoInicioRequisaFila2(tiempoInicioRequisa);
						double duracion = (Math.random() > 0.1) ? genInstExpo(15): genInstExpo(6);
						actual.setTiempoInicioRequisaFila1(Double.MAX_VALUE);
						actual.setTiempoSalida(tiempoInicioRequisa + duracion);

						cambiarTiempoEvento(actual);
						
						tiempoTerminacionUltimaRequisaFila2 = actual.getTiempoSalida();
						fila2.add(actual);			
					}
					
					for(int i=7;i<fila1.size();i++)
						fila1.remove(i);
					
					asociado.setFila(FILA_2);
					double tiempoInicioRequisa = Math.max(
							tiempoTerminacionUltimaRequisaFila2, tiempoActual);
					
					asociado.setTiempoInicioRequisaFila2(tiempoInicioRequisa);
					
					double duracion = (Math.random() > 0.1) ? genInstExpo(15)
							: genInstExpo(6);
					double tiempoSalida = tiempoInicioRequisa + duracion;

					asociado.setTiempoSalida(tiempoSalida);
					
					
					Evento requisa = new Evento(asociado.getTiempoSalida(), REQUISA, asociado);
					eventos.add(requisa);
					fila2.add(asociado);
					
				} else {
					asociado.setFila(FILA_1);

					double tiempoInicioRequisa = Math.max(
							tiempoTerminacionUltimaRequisaFila1, tiempoActual);

					double duracion = (Math.random() > 0.1) ? genInstExpo(15)
							: genInstExpo(6);
					double tiempoSalida = tiempoInicioRequisa + duracion;

					if (duracion > 0.25) {
						asociado.setTiempoInicioRequisaFila1(tiempoInicioRequisa);
						tiempoTerminacionUltimaRequisaFila1 = tiempoInicioRequisa + 0.25;

						double tiempoInicioFila2 = Math.max(
								tiempoTerminacionUltimaIncompletaFila2,
								tiempoInicioRequisa + 0.25);
						
						asociado.setFila(FILA_2);
						asociado.setTiempoInicioRequisaFila2(tiempoInicioFila2);
						asociado.setTiempoSalida(tiempoInicioFila2 + duracion
								- 0.25);

						tiempoTerminacionUltimaIncompletaFila2 = tiempoInicioFila2
								+ duracion - 0.25;

						Evento requisa = new Evento(asociado.getTiempoSalida(),
								REQUISA, asociado);
						eventos.add(requisa);
						
						fila1.add(asociado);
						tiempoTerminacionUltimaRequisaFila2 += duracion-0.25;
						desplazarEventos(duracion-0.25);
						
					} else {
						asociado.setTiempoInicioRequisaFila1(tiempoInicioRequisa);
						asociado.setTiempoSalida(tiempoSalida);
						
						fila1.add(asociado);
						Evento requisa = new Evento(asociado.getTiempoSalida(),
								REQUISA, asociado);
						eventos.add(requisa);
						tiempoTerminacionUltimaRequisaFila1 = tiempoSalida;
					}
				}

			} else if (tipo == REQUISA) {
				removerPasajeroFila(asociado);
			}

		}
	}

	
	/**
	 * Sincroniza el evento de salida con la información del asociado
	 * @param asociado el asociado con el cual se quiere sincronizar la información
	 */
	private void cambiarTiempoEvento(Pasajero asociado) {
		for(Evento e: eventos)
		{
			if(e.getAsociado().darId() == asociado.darId() && e.getTipo() == REQUISA)
			{
				e.setAsociado(asociado);
				e.setInicio(asociado.getTiempoSalida());
			}
		}	
	}

	/**
	 * Desplaza los eventos de salida y los atributos
	 * @param delta el tiempo en el que se desplazan los eventos
	 */
	private void desplazarEventos(double delta) 
	{
		for(Pasajero pasajero: fila2)
		{
			pasajero.setTiempoInicioRequisaFila2(pasajero.getTiempoInicioRequisaFila2()+delta);
			pasajero.setTiempoSalida(pasajero.getTiempoSalida()+delta);
		
			for(Evento e: eventos)
			{
				if(e.getTipo()==REQUISA && e.getAsociado().darId()==(pasajero.darId()))
				{
					e.setAsociado(pasajero);
					e.setInicio(e.getInicio()+delta);
				}
			}
		}
		
		
	}

	/**
	 * Crea un registro de la corrida en un archivo de texto
	 */
	private void imprimirCorrida() {
				
		Date d = new Date();
		File f = new File("./data/corrida escenario 2" + d.toString() + ".txt");
		try {
			f.createNewFile();
			FileWriter fw = new FileWriter(f);
			PrintWriter pw = new PrintWriter(fw);

			DecimalFormat df = new DecimalFormat("#.##");
			for (Pasajero pasajero : atendidos) {
				
				String inicio = pasajero.getTiempoInicioRequisaFila1() == Double.MAX_VALUE ? "-" : df.format(pasajero.getTiempoInicioRequisaFila1() );
				
				if (pasajero.getFila() == 1)
					System.out.println(pasajero.toString() + "\t"
							+ df.format(pasajero.getTiempoLlegada()) + '\t'
							+ inicio
							+ '\t'+ "-" + '\t'
							+ df.format(pasajero.getTiempoSalida()) + '\t'
							+ pasajero.getFila());
				else if (pasajero.getFila() == 2)
					System.out.println(pasajero.toString() + "\t"
							+ df.format(pasajero.getTiempoLlegada()) + '\t'
							+ inicio
							+ '\t'
							+ df.format(pasajero.getTiempoInicioRequisaFila2())
							+ '\t' + df.format(pasajero.getTiempoSalida())
							+ '\t' + pasajero.getFila() );
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Remueve el pasajero de su fila
	 * 
	 * @param removido
	 *            el pasajero a ser removido
	 */
	private void removerPasajeroFila(Pasajero removido) {
			fila1.remove(removido);
			fila2.remove(removido);
	}

	/**
	 * Da el próximo evento a suceder
	 * 
	 * @return el próximo evento
	 */
	private Evento darProximoEvento() {
		double min = Double.MAX_VALUE;
		Evento proximo = null;
		for (Evento evento : eventos) {
			if (evento.getInicio() <= min) {
				min = evento.getInicio();
				proximo = evento;
			}
		}
		return proximo;
	}

	/**
	 * Este procedimiento genera un tiempo exponencial con la tasa especificada
	 * en el parámetro lambda en las unidades del sistema.
	 * 
	 * @param lambda
	 *            la tasa
	 * @return un tiempo exponencial con la tasa especificada
	 */
	private double genInstExpo(double lambda) {
		double u = Math.random();
		return -Math.log(1 - u) / lambda;
	}

	/**
	 * Inicia la ejecución de la aplicación
	 * 
	 * @param arg0
	 *            parámetros pasados por el SO
	 */
	public static void main(String[] arg0) {

		for (int i = 0; i < 30; i++)
			new Principal2();
	}
}
